#! /usr/bin/python
from search import *
from strips_engine import *
from itertools import combinations
from copy import deepcopy


class PlanningProblem(Problem):
	def __init__(self, l, domain):
		self.name = l.next()
		if domain.name != l.next(): raise Exception("Domain name mismatch in Problem file")
		self.domain = domain
		self.objects = l.next()

		# checking object types 
		for obj in self.objects:
			if obj[1] not in domain.types: raise Exception("Object type mismatch in Problem file")

		self.initial =  VarifiedState(l.next(), self)
		for atom in self.initial.val:
			self._verify_ground_atom(atom)

		self.goal = VarifiedState(l.next(), self)
		for atom in self.goal.val:
			self._verify_ground_atom(atom)

	# verifies wheather an atom is correctly defined according to the domain predicates definition 
	def _verify_ground_atom(self, atom):
		# check ground atom names
		if atom[0] not in [x.name for x in self.domain.predicates]:
			raise Exception("Atom %s is not defined in the domain"%atom[0])
		# TODO 
		# check the number of parameters
		# check the type of each parameter

	# applys the effect of action act on state, and returns a new state
	def _effect(self,act, state):
		add = self._substitute(act.ADD, act.substitution)
		dell = self._substitute(act.DEL, act.substitution)

		newstate = deepcopy(state)
		for a in add:
			if a not in state.val: newstate.val.append(a)

		for a in dell:
			if a in state.val: newstate.val.remove(a)

		return newstate

	def goal_test(self, state):
		return  self.goal.subset(state)

	def successor(self, state):
		suc = []
		for act in self.domain.actions:
			if self._satisfy_action_preconditions(act, state):
				newstate =  self._effect(act,state)
				suc.append( (act, newstate))
				act.substition = []
		return suc
	def h(self,node):
		# count number of remaining goal predicates
		goalcount  = len(self.goal.val)
		count = 0
		for atom in node.state.val:
			if atom in self.goal.val: count += 1
		return  goalcount - count
	# return True if all pre conditions apply, False otherwise
	def _satisfy_action_preconditions(self, action, state):
		print "checking action: %s "%action,
		
		# creating substition list
		sub = []
		for obj in self.objects:
			for var in action.parameters:
				if obj[1] == var[1]: # same type
					if obj[0] in  state.objects:
						sub.append((var[0],obj[0]))
		comb = combinations(sub, len(action.variables))
		for c in comb:
			# removing doublicates TODO: requires enhancements (I should
			# rewrite the combination function using recursion for enhancements)
			s1 = set(e[0] for e in c) #vars are unique
			s2 = set(e[1] for e in c) #objs are unique
			if len(s1) == len(action.parameters) and len(s2) == len(action.parameters):
				substitute = self._substitute(action.preconditions, c)
				count = 0
				for x in substitute:
					if x in state.val: 
						count += 1
						#print "%s count "%action, count, "/",len(action.preconditions)

				# all preconditions satisfied
				if count == len(action.preconditions): 
					# store the substitution combination in action for effect use
					action.substitution = c
					print "[pass]"
					return True
		#print "action %s"%action, " not applicable"
		print "[fail]"
		return False
	# substitute new preconditions variables with objects by sub
	def _substitute(self, predicates, sub):
		sub_predicates = []
		for pre in predicates:
			param = []
			for var in pre[1]:
				for s in sub:
					if var == s[0]:
						param.append(s[1])
			sub_predicates.append((pre[0], param))
		return sub_predicates

class State:
	
	def __init__(self, l):
		self.val = l
		self.objects = []
		for pre in l:
			for obj in pre[1]:
				self.objects.append(obj)
		# remove doublicats 
		self.objects = list(set(self.objects))

	def __eq__(self,other):
		for pre in self.val:
			if pre not in other.val: return False
		for pre in other.val:
			if pre not in self.val: return False

		return True

	def subset(self, other):
		for pre in self.val:
			if pre not in other.val: return False
		return True

	def __hash__(self):
		if self.val == None:
			return None
		hv = 1
		for pre in self.val:
			for char in  pre[0]:
				hv  += ord(char)
			for obj in pre[1]:
				for s in obj:
					hv += ord(s)*10
		return hv



	def __repr__(self):
		str = ""
		for pre in self.val:
			str += pre[0] + "( "
			for v in pre[1]:
				str += v + ' '
			str += ')  '


		return  str
	#"state: " + self.val.__repr__()

# performs some check on the objects in state atoms. Useful for initial and goal test varification
class VarifiedState(State):
	def __init__(self, l, problem):
		self.val = l
		for pre in l:
			for obj in pre[1]:
				if obj not in [ x[0] for x in problem.objects]:
					raise Exception("Undeclared object %s type in problem file!"%obj)

		self.objects = []
		for pre in l:
			for obj in pre[1]:
				self.objects.append(obj)
		# remove doublicats 
		self.objects = list(set(self.objects))

if __name__ == "__main__":
	from copy import deepcopy
	problem = loadIntoProblem("pddl/domain1.pddl","pddl/problem1.pddl")
	#domain,problem = loadIntoProblem("domain1.pddl","err_problem1.pddl") #capture the error 
	ini = deepcopy(problem.initial)
	goal = deepcopy(problem.goal)


	print problem.h(Node(problem.goal))

	#n = breadth_first_graph_search(problem)
#
